﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using VsSourceCpoy.Entity;
using VsSourceCpoy.Helper;
using System.IO;

namespace VsSourceCpoy.Service
{
    public class Vs2008CSharpSolutionFileParser : ICSharpSlnFileParser
    {
        
        #region ICSharpSlnFileParser 成员
        public ParseResult ParseSlnFile(string fileName, Encoding encoding)
        {
            if (VsVersionCheck.GetVsVersionFromSlnFile(fileName) != VSVersion.VS2008)
            {
                throw new VersionUnsupportException("文件版本错误：" + fileName);
            }
            List<string> csprojFileList = GetCsprojFiles(fileName, encoding);
            ParseResult pr = new ParseResult();
            foreach (var item in csprojFileList)
            {
                pr.AddReslut(ParseCsprojFile(item, encoding));
            }
            pr.AllFileList.Add(fileName);
            pr.SlnFileList.Add(fileName);
            pr.CopyFromRoot = new DirectoryInfo(Path.GetDirectoryName(fileName));
            return pr;
        }

        public ParseResult ParseCsprojFile(string fileName, Encoding encoding)
        {
            if (VsVersionCheck.GetVsVersionFromCsprojFile(fileName) != VSVersion.VS2008)
            {
                throw new VersionUnsupportException("文件版本错误：" + fileName);
            }

            try
            {
                HashSet<string> hashSetFileName = GetCpoyPaths(fileName);
                HashSet<string> hashSetFileNameResult = new HashSet<string>();
                string fromFileDir = Path.GetDirectoryName(fileName);
                foreach (var item in hashSetFileName)
                {
                    hashSetFileNameResult.Add(Path.Combine(fromFileDir, item));
                }

                var res = new ParseResult(hashSetFileNameResult) { CopyFromRoot = new DirectoryInfo(fromFileDir) };

                res.CsprojFileList.Add(fileName);
                return res;
            }
            catch (Exception e)
            {
                throw new VersionUnsupportException("文件解析错误：" + fileName);
            }
        }
        #endregion
        private XNamespace xmlns = "http://schemas.microsoft.com/developer/msbuild/2003";
        private const string ELEMENT_PROJECT = "Project";
        private const string ELEMENT_COMPILE = "Compile";
        private const string ATTRIBUTE_INCLUDE = "Include";
        private const string ELEMENT_EMBEDDEDRESOURCE = "EmbeddedResource";
        private const string ELEMENT_NONE = "None";
        private const string ELEMENT_CONTENT = "Content";
        private const string ELEMENT_REFERENCE = "Reference";
        private const string ELEMENT_HINTPATH = "HintPath";
        private const string ELEMENT_APPLICATIONICON = "ApplicationIcon";

        private HashSet<string> GetCpoyPaths(string fileName)
        {
            XElement root = XElement.Load(fileName);
            HashSet<string> hashSetFileName = new HashSet<string>();
            hashSetFileName.Add(fileName);
            GetAtrributFromElement(hashSetFileName, root, ELEMENT_COMPILE, ATTRIBUTE_INCLUDE);
            GetAtrributFromElement(hashSetFileName, root, ELEMENT_EMBEDDEDRESOURCE, ATTRIBUTE_INCLUDE);
            GetAtrributFromElement(hashSetFileName, root, ELEMENT_NONE, ATTRIBUTE_INCLUDE);
            GetAtrributFromElement(hashSetFileName, root, ELEMENT_CONTENT, ATTRIBUTE_INCLUDE);
            GetElementValueFromElement(hashSetFileName, root, ELEMENT_REFERENCE, ELEMENT_HINTPATH);
            GetElementValueFromElement(hashSetFileName, root, ELEMENT_APPLICATIONICON, null);

            var referenceElement = root.Descendants(xmlns + ELEMENT_REFERENCE);
            var copunt = referenceElement.Count();

            var hintPathList = referenceElement.Where(
                element =>element.DescendantsAndSelf(xmlns + ELEMENT_HINTPATH).Count() > 0)
                .Select(eh =>eh.DescendantsAndSelf(xmlns + ELEMENT_HINTPATH).First().Value).ToList();

            foreach (var item in hintPathList)
            {
                hashSetFileName.Add(item);
            }
            return hashSetFileName;
        }

        private void GetAtrributFromElement(HashSet<string> value, XElement element, string elementName, string attributName)
        {
            var elements = element.Descendants(xmlns + elementName);
            foreach (var item in elements)
            {
                value.Add(item.Attribute(attributName).Value);
            }
        }

        private void GetElementValueFromElement(HashSet<string> value, XElement element, string mainElementName, string subElementName)
        {
            var elements = element.DescendantsAndSelf(xmlns + mainElementName); ;
            foreach (var item in elements)
            {
                if (!string.IsNullOrEmpty(subElementName))
                {
                    var subElements = item.Descendants(xmlns + subElementName);
                    if (subElements != null && subElements.Count() > 0)
                    {
                        value.Add(subElements.First().Value);
                    }
                }
                else
                {
                    if (elements != null && elements.Count() > 0)
                        value.Add(elements.First().Value);
                }
            }
        }
        private List<string> GetCsprojFiles(string fileName, Encoding encoding)
        {
            List<string> allLines = TxtFileHelper.ReadAllLines(fileName, new GetLineFilter()
            {
                CommentFilter = (line) => line.StartsWith("#"),
                DeleteEmptyLine = true,
                TrimLine = true
            });

            string dir = Path.GetDirectoryName(fileName);
            List<string> csProjList = new List<string>();
            List<string> projectStrs = null;
            try
            {
                projectStrs = GetProjectStr(allLines);
            }
            catch
            {
                throw new VersionUnsupportException("文件解析错误：" + fileName);
            }
            foreach (string item in projectStrs)
            {
                string[] pjSplit = item.Split(new char[] { ',' });
                string fileDir = "";
                if (pjSplit.Length >= 2)
                {
                    int commaFrom = pjSplit[1].IndexOf("\"");
                    int commaTo = pjSplit[1].LastIndexOf("\"");
                    fileDir = pjSplit[1].Substring(commaFrom + 1, commaTo - commaFrom - 1);
                    string resFileName = Path.Combine(dir, fileDir);
                    if (File.Exists(resFileName))
                        csProjList.Add(resFileName);
                }
            }

            return csProjList;
        }

        private List<string> GetProjectStr(List<string> allLines)
        {
            string endProject = "EndProject";
            string project = "Project";
            List<string> pList = new List<string>();
            bool projectIn = false;
            List<string> tempPjList = new List<string>();
            if (allLines == null) return pList;
            foreach (var item in allLines)
            {
                if (!projectIn)
                {
                    if (eq(item, endProject))
                    {
                        throw new ArgumentException("unexpected end :EndProject");
                    }
                    else if (item.StartsWith(project, StringComparison.OrdinalIgnoreCase))
                    {
                        projectIn = true;
                        tempPjList.Clear();
                        tempPjList.Add(item.Substring(project.Length));
                    }
                }
                else
                {
                    if (eq(item, endProject))
                    {
                        projectIn = false;
                        pList.Add(string.Join("\n", tempPjList));
                    }
                    else
                    {
                        tempPjList.Add(item);
                    }
                }
            }

            return pList;
        }

        private bool eq(string first, string second)
        {
            if (first == null)
            {
                if (second == null)
                    return true;
                return false;
            }
            return first.Equals(second, StringComparison.OrdinalIgnoreCase);
        }
    }
}
